package cn.jetpiece.cloud.joss.app.modular.object.service.impl;

import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.util.StrUtil;
import cn.jetpiece.cloud.core.util.HttpContext;
import cn.jetpiece.cloud.joss.api.object.model.enums.PutObjectModelEnum;
import cn.jetpiece.cloud.joss.api.object.model.params.CompleteMultiPartUploadObject;
import cn.jetpiece.cloud.joss.api.object.model.params.InitMultiPartUploadObject;
import cn.jetpiece.cloud.joss.api.object.model.params.MultiPartUploadObject;
import cn.jetpiece.cloud.joss.api.object.model.params.PutObject;
import cn.jetpiece.cloud.joss.api.object.model.result.JossPartEtag;
import cn.jetpiece.cloud.joss.api.object.model.result.UploadObjectResult;
import cn.jetpiece.cloud.joss.app.config.properties.OssProperties;
import cn.jetpiece.cloud.joss.app.core.constant.OssConstants;
import cn.jetpiece.cloud.joss.app.core.utils.AuthUtil;
import cn.jetpiece.cloud.joss.app.modular.basics.service.ContentTypeService;
import cn.jetpiece.cloud.joss.app.modular.object.enums.BasicExceptionEnum;
import cn.jetpiece.cloud.joss.app.modular.object.model.BucketInfo;
import cn.jetpiece.cloud.joss.app.modular.object.service.ObjectService;
import cn.jetpiece.cloud.joss.app.modular.storage.enums.OssObjectExceptionEnum;
import cn.jetpiece.cloud.joss.app.modular.storage.factory.JossClientFactory;
import cn.jetpiece.cloud.joss.app.modular.storage.service.JossClient;
import cn.jetpiece.cloud.model.exception.ServiceException;
import cn.jetpiece.cloud.model.response.Result;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;

/**
 * <p>
 * 对象 服务实现类
 * </p>
 *
 * @author XuJZ
 * @since 2020-09-05
 */
@Slf4j
@Service
@AllArgsConstructor
public class ObjectServiceImpl implements ObjectService {
    private final OssProperties ossProperties;
    private final ContentTypeService contentTypeService;

    @Override
    public String initMultipartUpload(String objectName, InitMultiPartUploadObject param, BucketInfo bucketInfo) {
        if (param.getAcl() == null) {
            param.setAcl(bucketInfo.getAcl());
        }
        if (StrUtil.isBlank(param.getContentType())) {
            String suffixName = null;
            if (objectName.contains(OssConstants.SUFFIX_NAME_SEPARATE)) {
                suffixName = objectName.substring(objectName.lastIndexOf(OssConstants.SUFFIX_NAME_SEPARATE));
            }
            String contentType;
            if (suffixName != null && suffixName.length() > 0) {
                contentType = contentTypeService.getContentType(suffixName);
            } else {
                contentType = "application/octet‑stream";
            }
            param.setContentType(contentType);
        }
        JossClient jossClient = JossClientFactory.getJossClient(bucketInfo);
        return jossClient.initMultipartUpload(bucketInfo, objectName, param);
    }

    @Override
    public JossPartEtag multipartUpload(String objectName, MultiPartUploadObject param, BucketInfo bucketInfo, MultipartFile file) {
        JossClient jossClient = JossClientFactory.getJossClient(bucketInfo);
        return jossClient.multipartUpload(bucketInfo, objectName, param, file);
    }

    @Override
    public Result<UploadObjectResult> completeMultipartUpload(String objectName, CompleteMultiPartUploadObject param, BucketInfo bucketInfo) {
        JossClient jossClient = JossClientFactory.getJossClient(bucketInfo);
        UploadObjectResult result = jossClient.completeMultipartUpload(bucketInfo, objectName, param);
        result.setUrl(getObjectUrl(bucketInfo.getBucketName(), result.getObjectName()));
        return Result.OK(result);
    }

    @Override
    public Result<UploadObjectResult> putObject(String objectName, PutObject putObject, BucketInfo bucketInfo, MultipartFile file) {
        if (putObject.getPutObjectMode().equals(PutObjectModelEnum.FILE_STREAM.getCode())) {
            return putObjectFile(objectName, putObject, bucketInfo, file);
        } else if (putObject.getPutObjectMode().equals(PutObjectModelEnum.BASE64.getCode())) {
            return putObjectBase64(objectName, putObject, bucketInfo);
        } else if (putObject.getPutObjectMode().equals(PutObjectModelEnum.URL.getCode())) {
            return putObjectUrl(objectName, putObject, bucketInfo);
        }
        return Result.error(OssObjectExceptionEnum.NOT_SUPPORT_PUT_OBJECT_MODE);
    }

    @Override
    public void getObject(String objectName, BucketInfo bucketInfo) {
        JossClientFactory.getJossClient(bucketInfo).getObject(bucketInfo, objectName, AuthUtil.isNotAuth());
    }

    @Override
    public void deleteObject(String objectName, BucketInfo bucketInfo) {
        JossClientFactory.getJossClient(bucketInfo).delete(bucketInfo, objectName, AuthUtil.isNotAuth());
    }

    private Result<UploadObjectResult> putObjectFile(String objectName, PutObject putObject, BucketInfo bucketInfo, MultipartFile file) {
        InputStream inputStream;
        String contentType = null;
        long objectSize;
        try {
            if (file == null) {
                inputStream = HttpContext.getRequest().getInputStream();
                objectSize = HttpContext.getRequest().getContentLengthLong();
                if (objectSize == -1) {
                    objectSize = inputStream.available();
                }
            } else {
                inputStream = file.getInputStream();
                contentType = file.getContentType();
                objectSize = file.getSize();
            }
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(BasicExceptionEnum.OPEN_INPUT_STREAM_ERROR);
        }
        String suffixName = null;
        if (objectName.contains(OssConstants.SUFFIX_NAME_SEPARATE)) {
            suffixName = objectName.substring(objectName.lastIndexOf(OssConstants.SUFFIX_NAME_SEPARATE));
        }
        contentType = getContextType(contentType,putObject,suffixName);

        UploadObjectResult result = new UploadObjectResult();
        result.setObjectName(objectName);
        result.setContextType(contentType);
        result.setObjectSize(objectSize);
        result.setBucketName(bucketInfo.getBucketName());
        result.setProviderUrl(JossClientFactory.getJossClient(bucketInfo).upload(bucketInfo, putObject.getAcl(), objectName, objectSize, contentType, inputStream));
        result.setUrl(getObjectUrl(bucketInfo.getBucketName(), objectName));
        return Result.OK(result);
    }

    private String getContextType(String contentType,PutObject putObject,String suffixName){
        if (StrUtil.isNotBlank(putObject.getContentType())) {
            return putObject.getContentType();
        }
        if ((contentType == null || contentType.equals("application/octet-stream")) && suffixName != null && suffixName.length() > 0) {
            contentType = contentTypeService.getContentType(suffixName);
        }
        if (contentType == null) {
            contentType = HttpContext.getRequest().getContentType();
        }
        return contentType;
    }

    private Result<UploadObjectResult> putObjectBase64(String objectName, PutObject putObject, BucketInfo bucketInfo) {
        HttpServletRequest request = HttpContext.getRequest();
        byte[] bytes = new byte[request.getContentLength()];
        String suffixName = null;
        String base64Header = null;
        if (objectName.contains(OssConstants.SUFFIX_NAME_SEPARATE)) {
            suffixName = objectName.substring(objectName.lastIndexOf(OssConstants.SUFFIX_NAME_SEPARATE));
        }
        try {
            IOUtils.readFully(request.getInputStream(), bytes);
            String base64 = new String(bytes);
            if (base64.contains(OssConstants.COMMA)) {
                base64Header = base64.substring(0, base64.indexOf(OssConstants.COMMA));
                base64 = base64.substring(base64.indexOf(OssConstants.COMMA) + 1);
            }
            base64 = base64.replaceAll("!", "\\+").replaceAll("-", "/");
            bytes = Base64.decodeBase64(base64);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(BasicExceptionEnum.BASE64_FORMAT_EXCEPTION);
        }
        String contentType = null;
        if (suffixName != null && suffixName.length() > 0) {
            contentType = contentTypeService.getContentType(suffixName);
        }
        if (contentType == null && base64Header != null) {
            contentType = base64Header.substring(base64Header.indexOf(":") + 1, base64Header.indexOf(";"));
        }
        if (contentType == null) {
            suffixName = FileTypeUtil.getType(new ByteArrayInputStream(bytes));
            if (suffixName != null && suffixName.length() > 0) {
                contentType = contentTypeService.getContentType(suffixName);
            }
        }
        if (StrUtil.isNotBlank(putObject.getContentType())) {
            contentType = putObject.getContentType();
        }
        UploadObjectResult result = new UploadObjectResult();
        result.setObjectName(objectName);
        result.setContextType(contentType);
        result.setObjectSize((long) bytes.length);
        result.setBucketName(bucketInfo.getBucketName());
        result.setProviderUrl(JossClientFactory.getJossClient(bucketInfo).upload(bucketInfo, putObject.getAcl(), objectName, bytes.length, contentType, bytes));
        result.setUrl(getObjectUrl(bucketInfo.getBucketName(), objectName));
        return Result.OK(result);
    }

    private Result<UploadObjectResult> putObjectUrl(String objectName, PutObject putObject, BucketInfo bucketInfo) {
        InputStream inputStream;
        HttpServletRequest request = HttpContext.getRequest();
        byte[] bytes = new byte[request.getContentLength()];
        String contentType;
        long objectSize;
        try {
            IOUtils.readFully(request.getInputStream(), bytes);
            String content = new String(bytes);

            /* 适配Feign调用地址多了前后双引号问题 */
            if (content.startsWith("\"")) {
                content = content.substring(1);
            }
            if (content.endsWith("\"")) {
                content = content.substring(0, content.length() - 1);
            }
            URL url = new URL(content);
            inputStream = url.openStream();
            URLConnection urlConnection = url.openConnection();
            objectSize = urlConnection.getContentLengthLong();
            contentType = urlConnection.getContentType();
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            throw new ServiceException(BasicExceptionEnum.URL_ACCESS_EXCEPTION);
        }
        if (StrUtil.isNotBlank(putObject.getContentType())) {
            contentType = putObject.getContentType();
        }
        UploadObjectResult result = new UploadObjectResult();
        result.setObjectName(objectName);
        result.setContextType(contentType);
        result.setObjectSize(objectSize);
        result.setBucketName(bucketInfo.getBucketName());
        result.setProviderUrl(JossClientFactory.getJossClient(bucketInfo).upload(bucketInfo, putObject.getAcl(), objectName, objectSize, contentType, inputStream));
        result.setUrl(getObjectUrl(bucketInfo.getBucketName(), objectName));
        return Result.OK(result);
    }

    @Override
    public String getObjectUrl(String bucketName, String objectName) {
        StringBuilder builder = new StringBuilder();
        builder.append(ossProperties.getBasePreviewUrl());
        if (!ossProperties.getBasePreviewUrl().endsWith(OssConstants.PATH_SEPARATE)) {
            builder.append(OssConstants.PATH_SEPARATE);
        }
        builder.append(bucketName);
        if (!objectName.startsWith(OssConstants.PATH_SEPARATE)) {
            builder.append(OssConstants.PATH_SEPARATE);
        }
        builder.append(objectName);
        return builder.toString();
    }
}
